package com.ansion.utils.bus

import android.content.IntentFilter
import com.ansion.log.VLog
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock

/**
 * Support two strategy
 * 1. register and post run in current thread.
 *    EventBusIntentFilter and Event need set thread mode param
 *
 * 2. register and post run in coroutines
 */
object EventBus {

    private class ReceiverRecord(val filter: IntentFilter, val receiver: BusReceiver) {
        var broadcasting = false
        var dead = false
    }

    private val mReceivers = HashMap<BusReceiver, ArrayList<ReceiverRecord>>()
    private val mActions: HashMap<String, ArrayList<ReceiverRecord>> = HashMap()
    private val mutableChannel: Channel<Event> = Channel()
    private val mChannelFlow: Flow<Event> = mutableChannel.receiveAsFlow()
    private val mMutex = Mutex()

    init {
        BGScope().launch {
            mChannelFlow.collect {
                mMutex.withLock {
                    sendBus(it)
                }
            }
        }
    }

    fun post(intent: Event) {
        when (intent.getThreadMode()) {
            EventBusThreadMode.CURRENT -> {
                runBlocking {
                    mMutex.withLock {
                        sendBus(intent)
                    }
                }
            }

            EventBusThreadMode.COROUTINE -> {
                BGScope().launch {
                    VLog.d("TAG_BUS", "PostEvent: $intent")
                    mutableChannel.send(intent)
                }
            }
        }
    }

    fun register(listener: BusReceiver, filter: EventBusIntentFilter) {
        if (filter.getThreadMode() == EventBusThreadMode.COROUTINE) {
            BGScope().launch {
                mMutex.withLock {
                    registerReceiver(listener, filter)
                }
            }
        }

        if (filter.getThreadMode() == EventBusThreadMode.CURRENT) {
            runBlocking {
                mMutex.withLock {
                    registerReceiver(listener, filter)
                }
            }
        }
    }

    fun unregister(receiver: BusReceiver) {
        runBlocking {
            mMutex.withLock {
                unregisterReceiver(receiver)
            }
        }
    }

    private fun registerReceiver(
        receiver: BusReceiver,
        filter: IntentFilter,
    ) {
        val entry = ReceiverRecord(filter, receiver)
        var filters: ArrayList<ReceiverRecord>? = mReceivers[receiver]
        if (filters == null) {
            filters = ArrayList(1)
            mReceivers[receiver] = filters
        }
        filters.add(entry)
        for (i in 0 until filter.countActions()) {
            val action = filter.getAction(i)
            var entries: ArrayList<ReceiverRecord>? = mActions[action]
            if (entries == null) {
                entries = ArrayList(1)
                mActions[action] = entries
            }
            entries.add(entry)
        }
    }

    private fun unregisterReceiver(receiver: BusReceiver) {
        val filters = mReceivers.remove(receiver) ?: return

        for (i in filters.indices.reversed()) {
            val filter: ReceiverRecord = filters[i]
            filter.dead = true
            for (j in 0 until filter.filter.countActions()) {
                val action: String = filter.filter.getAction(j)
                val receivers: ArrayList<ReceiverRecord>? = mActions[action]
                if (receivers != null) {
                    for (k in receivers.indices.reversed()) {
                        val rec: ReceiverRecord = receivers[k]
                        if (rec.receiver === receiver) {
                            rec.dead = true
                            receivers.removeAt(k)
                        }
                    }
                    if (receivers.size <= 0) {
                        mActions.remove(action)
                    }
                }
            }
        }
    }

    private suspend fun sendBus(intent: Event) {
        val action = intent.action
        val type = intent.getType()
        val entries: ArrayList<ReceiverRecord>? = mActions[intent.action]

        if (entries != null) {
            VLog.d("TAG_BUS", "Action list: $entries")
            var receivers: ArrayList<ReceiverRecord>? = null
            for (i in entries.indices) {
                val receiver: ReceiverRecord = entries[i]
                VLog.d("TAG_BUS", "Matching against filter " + receiver.filter)
                if (receiver.broadcasting) {
                    VLog.d("TAG_BUS", "  Filter's target already added")
                    continue
                }
                val match: Int = receiver.filter.match(
                    action, type, null, null, null, "BotBus"
                )
                if (match >= 0) {
                    VLog.d(
                        "TAG_BUS", "  Filter matched!  match=0x" + Integer.toHexString(match)
                    )
                    if (receivers == null) {
                        receivers = ArrayList()
                    }
                    receivers.add(receiver)
                    receiver.broadcasting = true
                } else {
                    val reason: String = when (match) {
                        IntentFilter.NO_MATCH_ACTION -> "action"
                        IntentFilter.NO_MATCH_CATEGORY -> "category"
                        IntentFilter.NO_MATCH_DATA -> "data"
                        IntentFilter.NO_MATCH_TYPE -> "type"
                        else -> "unknown reason"
                    }
                    VLog.d("TAG_BUS", "  Filter did not match: $reason")
                }
            }
            if (receivers != null) {
                for (i in receivers.indices) {
                    receivers[i].broadcasting = false
                }

                executePendingBroadcasts(receivers, intent)

            }
        }
    }

    private suspend fun executePendingBroadcasts(receivers: ArrayList<ReceiverRecord>, intent: Event) {
        for (i in receivers.indices) {
            val receiver: ReceiverRecord = receivers[i]
            if (!receiver.dead) {
                receiver.receiver.onReceive(intent)
            }
        }
    }

    fun createFilter(action: String, type: String?, threadMode: EventBusThreadMode = EventBusThreadMode.COROUTINE): EventBusIntentFilter {
        val filter = EventBusIntentFilter(action)
        filter.setThreadMode(threadMode)
        if (type != null) {
            filter.addDataType("bus/$type")
        }
        return filter
    }

    fun createFilter(action: String, type: String?): EventBusIntentFilter {
        val filter = EventBusIntentFilter(action)
        if (type != null) {
            filter.addDataType("bus/$type")
        }
        return filter
    }
}
